package dkbta.processing;

import java.lang.ref.WeakReference;
import java.util.WeakHashMap;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

import dkbta.processing.tasks.Task;

public class WorkQueue{
	/**
	 * A cache of all the tasks that were created. It is used in order not to create tasks
	 * twice. Tasks of the same kind must be singleton for the parent/child relationships
	 * between tasks will work properly. Tasks that have been completed are automatically
	 * discarded by the WeakHashMap
	 */
	private final WeakHashMap<Task, WeakReference<Task>> _allTasks;

	/** The actual work queue */
	private final BlockingQueue<Task> _pendingTasks;

	/**
	 * The number of primary tasks left to complete. The queue stops waiting on additional
	 * tasks only when this number reaches zero
	 */
	private int _primaryTasksLeft;

	public WorkQueue(){
		_allTasks = new WeakHashMap<Task, WeakReference<Task>>();
		_pendingTasks = new LinkedBlockingQueue<Task>();
		_primaryTasksLeft = 0;
	}

	/**
	 * Adds a task directly to the work queue
	 * 
	 * @param task The task to be added
	 */
	public void addTask(Task task){
		_pendingTasks.add(task);
	}
	
	private volatile Task _currentTask;

	/**
	 * Sort of a getter for the single instance of the new task. The given Task object is
	 * used to look up a previous matching instance if such exists, if not it is the new
	 * task. Also adds the task to the work queue if requested to.
	 * 
	 * @param newTask A lookup object / the task itself
	 * @param addToWorkQueue Whether to add the task to the work queue
	 * @return The single instance of the task (the lookup object if no previous matching
	 *         task existed, otherwise the existing task)
	 */
	public Task newTask(Task newTask, boolean addToWorkQueue){
		WeakReference<Task> existingTask = _allTasks.get(newTask);
		if (existingTask == null){
			// Saving the task in the cache
			_allTasks.put(newTask, new WeakReference<Task>(newTask));
			if (addToWorkQueue){
				addTask(newTask);
			}
			if (newTask.isPrimary){
				++_primaryTasksLeft;
			}
			return newTask;
		}else{
			return existingTask.get();
		}
	}

	/**
	 * Starts performing all the tasks. This method will return once all primary tasks
	 * have been completed.
	 */
	public void work(){
		try{
			while (_primaryTasksLeft != 0){
				Task task = _pendingTasks.take();
				_currentTask = task;
				boolean completed = task.performTask(this);
				if (completed && task.isPrimary){
					--_primaryTasksLeft;
				}
			}
		}catch(InterruptedException e){
			throw new RuntimeException("Working queue thread was interrupted");
		}
	}

	@Override
	public String toString(){
		StringBuilder sb = new StringBuilder("Work Queue: ");
		sb.append("\r\nPending Tasks:\r\n");
		for (Task t : _pendingTasks){
			sb.append("\t").append(t).append("\r\n");
		}
		sb.append("\r\nCurrent Task:\r\n");
			sb.append("\t").append(_currentTask).append("\r\n");
		sb.append("\r\n");
		return sb.toString();
	}
}
